#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stddef.h>
#include <time.h>

#include "debug.h"
#include "rsc.h"
#include "mav.h"
#include "mav_db.h"

static MAV_HANDLE pcurrent_mav; /* I just have no better idea:init/exit/seek_current */

static MAV_HANDLE mav_prepare_env(const char *filename, int strtype)
{
    if(filename==NULL)
    {
	printf("invalid mav file name!\n");
	return NULL;
    }
    if(strlen(filename)>=64)
    {
	printf("mav filename[%s] is greater than 64.\n",filename);
	return NULL;
    }
    if(strtype < MAV_STREAM_VIDEO || strtype >= MAV_STREAM_MAX)
    {
	printf("stream type[%d] is invalid!\n",strtype);
	return NULL;
    }
    MAV_HANDLE pmav = NULL;
    pmav = calloc(1,sizeof(struct mav_handle));
    if(pmav == NULL)
    {
	printf("calloc pmav memory fail!\n");
	return NULL;
    }
    /* initial index number to 100 */
    pmav->index = calloc(1,sizeof(struct mav_index_list)+MAV_INITIAL_INDEX_NUM*sizeof(st_mav_index_item));
    if(pmav->index == NULL)
    {
	printf("calloc mav index memory fail!\n");
	free(pmav);
	return NULL;
    }
    pmav->index_max = MAV_INITIAL_INDEX_NUM;
    /// open file to write 
    FILE *file = fopen(filename,"wb");
    if(file == NULL)
    {
	printf("fopen file %s err:%s\n",filename,strerror(errno));
	free(pmav->index);
	free(pmav);
	return NULL;
    }
    strcpy(pmav->fname,filename);
    pmav->fp = file;
    return pmav;
}

static int mav_write_header(struct mav_handle *pmav, int strtype)
{
    int ret = 0;
    // header list
    pmav->header.mavh = mkFOURCC('M','A','V','I');
    pmav->header.index_interval = 1; // default
    /* need to update when exit handle */
    pmav->header.index_offset = sizeof(pmav->header);// at least
    pmav->header.index_num = 0;

    // index
    pmav->index->magic = MAV_INDEX_MAGIC;
    pmav->index->sum = 0;

    // write header to file
    ret = fwrite(&pmav->header,sizeof(pmav->header),1,pmav->fp);
    if(ret != 1)
	ret = -1;
    else 
	ret = 0;
    fflush(pmav->fp);

    return ret;
}
MAV_HANDLE mav_init_handle(const char *filename,int strtype)
{
    int ret = 0;

    MAV_HANDLE pmav = mav_prepare_env(filename,strtype);
    if(pmav == NULL)
    {
	printf("mav prepare handle env fail!\n");
	return NULL;
    }

    ret = mav_write_header(pmav,strtype);

    if(ret < 0)
    {
	printf("mav_write_header error:%d\n",ret);
	fclose(pmav->fp);
	free(pmav->index);
	free(pmav);
	return NULL;
    }

    pcurrent_mav = pmav;
    return pmav;
}

int mav_write_packet(MAV_HANDLE pmav,int type,unsigned char *buf,int size)
{
    if(pmav==NULL || buf==NULL || size <= 0)
    {
	printf("invalid parameters!\n");
	return -1;
    }
    if(type != MAV_STREAM_VIDEO && type != MAV_STREAM_AUDIO)
    {
	printf("stream type is invalid,only 0(video) and 1(audio) ok.\n");
	return -1;
    }

    int ret = 0;
    long offset = ftell(pmav->fp);

    // write media data to mav file
    ret = fwrite((void*)buf,size,1,pmav->fp);	
    if(ret != 1)
    {
	printf("fwrite mav packet err:%s\n",strerror(errno));
	ret = RSC_EWRIT;
    }
    else
    { /// update mav index info 
	struct mav_frame_header fheader;
	int sum = pmav->index->sum;
	memcpy(&fheader,buf,sizeof(fheader));
	if(fheader.type==MAV_FRAME_ISLICE)
	{
	    if(pmav->index->sum==pmav->index_max)
	    {// increase index space firstly
		size_t new_index_size = 0;
		new_index_size = sizeof(struct mav_index_list)+(pmav->index_max+MAV_INITIAL_INDEX_NUM)*sizeof(st_mav_index_item);
		pmav->index = realloc(pmav->index,new_index_size);
		if(pmav->index==NULL)
		{
		    printf("realloc index size[%d] fail:%s\n",new_index_size,strerror(errno));
		    return -1; 
		}
		pmav->index_max += MAV_INITIAL_INDEX_NUM;
		//printf("index sum = %d,max = %d(init:100),memory size = %d B\n",sum,pmav->index_max,new_index_size);
	    }
	    pmav->index->item[sum].index = pmav->index->sum;
	    pmav->index->item[sum].timestamp = fheader.fts;
	    pmav->index->item[sum].offset = offset;
	    pmav->index->item[sum].type = fheader.type;
	    pmav->index->sum++;
	}
    }

    if(ret < 0) 
	return ret;
    return 0;
}

static int mav_write_trailer(MAV_HANDLE pmav)
{
    fflush(pmav->fp);

    unsigned long index_offset = ftell(pmav->fp);
    unsigned long total_index = pmav->index->sum;
    unsigned long index_size = 0;

    /* write mav index to file trailer */
    index_size = offsetof(struct mav_index_list,item)+total_index*sizeof(st_mav_index_item);
    fwrite(pmav->index,index_size,1,pmav->fp);

    /* update mav header info */
    printf("---total_index = %lu\n",total_index);
    pmav->header.index_offset = index_offset;
    pmav->header.index_num = total_index;
    pmav->header.index_begin = pmav->index->item[0];
    if(total_index>0)
	pmav->header.index_end = pmav->index->item[total_index-1];
    else
	pmav->header.index_end = pmav->header.index_begin;

    fseek(pmav->fp,0,SEEK_SET);
    fwrite((void*)&pmav->header,sizeof(pmav->header),1,pmav->fp);

    fflush(pmav->fp);
    fdatasync(fileno(pmav->fp));
    fclose(pmav->fp);

    pcurrent_mav = NULL;
    return 0;
}
void mav_exit_handle(MAV_HANDLE pmav)
{
    if(pmav!=NULL)
    {
	mav_write_trailer(pmav);
	mav_db_add_item(pmav->fname);// update mav db list 
	free(pmav->index);
	free(pmav);
	pmav = NULL;
    }
}
static struct mav_index_list *mav_rebuild_index(FILE *fp)
{
    rewind(fp);
    fseek(fp,sizeof(struct mav_header_list),SEEK_SET);

    struct mav_frame_header fheader;
    struct mav_index_list *index = NULL;
    int nread = 0,index_max = 0;
    /* initial index number to 100 */
    index = calloc(1,sizeof(struct mav_index_list)+MAV_INITIAL_INDEX_NUM*sizeof(st_mav_index_item));
    if(index == NULL)
    {
	printf("calloc mav index memory fail!\n");
	return NULL;
    }
    index_max = MAV_INITIAL_INDEX_NUM;
    index->magic = MAV_INDEX_MAGIC;
    index->sum = 0;
    long offset = 0;
    while(!feof(fp))
    {
	offset = ftell(fp);
	nread = fread(&fheader,sizeof(fheader),1,fp);
	if(nread!=1)
	{
	    printf("fread header fail,nread = %d!\n",nread);
	    break;;
	}
	else if(fheader.type<MAV_FRAME_ISLICE||fheader.type>MAV_FRAME_ASLICE)
	{
	    printf("frame header type[%d] invalid!\n",fheader.type);
	    break;
	}
	else if(fheader.type==MAV_FRAME_ISLICE)
	{
	    int sum = index->sum;
	    if(index->sum==index_max)
	    {// increase index space firstly
		size_t new_index_size = 0;
		new_index_size = sizeof(struct mav_index_list)+(index_max+MAV_INITIAL_INDEX_NUM)*sizeof(st_mav_index_item);
		index = realloc(index,new_index_size);
		if(index==NULL)
		{
		    printf("rebuild realloc index [%d] fail:%s\n",new_index_size,strerror(errno));
		    break;
		}
		index_max += MAV_INITIAL_INDEX_NUM;
	    }
	    index->item[sum].index = index->sum;
	    index->item[sum].timestamp = fheader.fts;
	    index->item[sum].offset = offset;
	    index->item[sum].type = fheader.type;
	    index->sum++;
	}
	/** skip to next frame header */
	fseek(fp,fheader.len,SEEK_CUR);
    }
    clearerr(fp);
    return index;
}

int mav_fix_handle(const char *filename)
{
    if(filename==NULL)
    {
	printf("filename is NULL!\n");
	return  -1;
    }

    FILE *fp = fopen(filename,"rb+");

    if(fp == NULL)
    {
	printf("fopen [%s] err:%s\n",filename,strerror(errno));
	return -1;
    }
    /***** ensure this is a MAV file ****/
    struct mav_header_list header;
    int ret = 0;
    memset(&header,0,sizeof(header));
    if(fread(&header,1,sizeof(header),fp)!=sizeof(header))
    {
	printf("fread mavh header fail!\n");
	ret = -1;
    }
    else if(header.mavh!=mkFOURCC('M','A','V','I'))
    {
	printf("header is not \'MAVI\'!\n");
	ret = -1;
    }
    else if(header.index_offset<=sizeof(header)||header.index_num==0)
    {
	printf("---offset = %ld,index num = %d\n",header.index_offset,header.index_num);
	struct mav_index_list *index = NULL;
	index = mav_rebuild_index(fp);
	if(index==NULL)
	{
	    printf("!! rebuild mav index fail!\n");
	    ret = -1;
	}
	else
	{ // then update header list
	    unsigned long index_offset = ftell(fp);
	    unsigned long total_index = index->sum;
	    unsigned long index_size = 0;

	    /* write mav index to file trailer */
	    index_size = offsetof(struct mav_index_list,item)+total_index*sizeof(st_mav_index_item);
	    fwrite(index,index_size,1,fp);

	    /* update mav header info */
	    printf("---index offset = %lu,sum = %u\n",index_offset,total_index);
	    header.index_offset = index_offset;
	    header.index_num = total_index;
	    header.index_begin = index->item[0];
	    if(total_index>0)
		header.index_end = index->item[total_index-1];
	    else
		header.index_end = header.index_begin;

	    printf("begin: index = %u,ts = %ld,offset = %ld,type = %d\n",
		    header.index_begin.index,header.index_begin.timestamp,header.index_begin.offset,header.index_begin.type);
	    printf("end: index = %u,ts = %ld,offset = %ld,type = %d\n",
		    header.index_end.index,header.index_end.timestamp,header.index_end.offset,header.index_end.type);

	    free(index);
	    fseek(fp,0,SEEK_SET);
	    fwrite((void*)&header,sizeof(header),1,fp);
	    fflush(fp);
	    fdatasync(fileno(fp));
	}
    }

    fclose(fp);

    return ret;
}

long mav_seek_packet(FILE *fp,time_t tnode)
{
    struct mav_header_list header;
    long offset = 0;
    int ret = 0;
    memset(&header,0,sizeof(header));
    if(fread(&header,1,sizeof(header),fp)!=sizeof(header))
    {
	printf("fread mavh header fail!\n");
	ret = -1;
    }
    else if(header.mavh!=mkFOURCC('M','A','V','I'))
    {
	printf("header is not \'MAVI\'!\n");
	ret = -1;
    }
    else if(header.index_offset<=sizeof(header)||header.index_num==0)
    {
	printf("header index offset or index num is incorrect!\n");
	ret = -1;
    }
    else if(tnode <= header.index_begin.timestamp || tnode>=header.index_end.timestamp)
    {
	printf("tnode[%ld] not between the file,set offset to 0!\n",tnode);
	offset = sizeof(header); /* skip mavi header */
    }
    else
    {// calculate index item position and read offset from index list
	unsigned int item_offset = header.index_offset+offsetof(struct mav_index_list,item);
	int flag = 0;
	fseek(fp,item_offset,SEEK_SET);
	printf("---item_offset = %u.\n",item_offset);
	st_mav_index_item node_item[2];
	while((ret = fread(node_item,sizeof(st_mav_index_item),2,fp))==2)
	{
	    flag = 1;
	    if(tnode>=node_item[0].timestamp && tnode<=node_item[1].timestamp)
	    {
		offset = node_item[0].offset;
		break;
	    }
	}
	if(ret!=2)
	{
	    if(ret==1)
	    {
		printf("it's last one frame in file,timestamp = %ld !\n",node_item[0].timestamp);
		offset = node_item[0].offset;
	    }
	    else if(ret==0&&flag==1)
	    {
		printf("it's last two frame in file,timestamp = %ld !\n",node_item[1].timestamp);
		offset = node_item[1].offset;
	    }
	    else
	    {
		printf("fread node index item fail,ret = %d(%s}!\n",ret,(ret<0)?strerror(errno):"fail");
		ret = -1;
	    }
	}
	else	
	    ret = 0;
    }

    if(ret < 0)
	return -1;
    return offset;
}

FILE* mav_seek_frame(const char *filename,time_t tnode)
{
    if(filename==NULL)
    {
	printf("filename is NULL!\n");
	return  NULL;
    }

    FILE *fp = fopen(filename,"rb");
    long offset = 0;

    if(fp == NULL)
    {
	printf("fopen [%s] err:%s\n",filename,strerror(errno));
	return NULL;
    }
    offset = mav_seek_packet(fp,tnode);
    if(offset < 0)
    {
	printf("mav_seek_packet tnode[%ld] in %s fail!\n",tnode,filename);
	fclose(fp);
	return NULL;
    }
    fseek(fp,offset,SEEK_SET);

    return fp;
}

int mav_read_header(const char *filename,struct mav_header_list *header)
{
    if(filename==NULL)
    {
	printf("filename is NULL!\n");
	return  -1;
    }

    FILE *fp = fopen(filename,"rb");
    int ret = 0;

    if(fp == NULL)
    {
	printf("fopen [%s] err:%s\n",filename,strerror(errno));
	return -1;
    }
    ret = fread(header,sizeof(struct mav_header_list),1,fp);
    if(ret != 1)
    {
	printf("fread mav header fail,ret = %d\n",ret);
	fclose(fp);
	remove(filename);
	return -1;
    }
    else if(header->mavh!=mkFOURCC('M','A','V','I'))
    {
	printf("header is not \'MAVI\'!\n");
	fclose(fp);
	remove(filename);
	return -1;
    }
    fclose(fp);
    return ret;
}

int mav_get_tszone(const char *filename,time_t *pstart,time_t *pend)
{
    struct mav_header_list header;

    if(mav_read_header(filename,&header)<0)
    {
	printf("mav_read_header fail!\n");
	return -1;
    }
    if(header.index_begin.timestamp==0||header.index_end.timestamp==0)
    {
	mav_fix_handle(filename);
	if(mav_read_header(filename,&header)<0)
	{
	    printf("mav_read_header fail!\n");
	    return -1;
	}
	else if(header.index_begin.timestamp==0||header.index_end.timestamp==0)
	{
	    printf("index begin & end timestamp is 0!\n");
	    remove(filename);
	    return -1;
	}
    }
//    printf("index offset = %ld,interval = %u,num = %u\n",header.index_offset,header.index_interval,header.index_num);
//    printf("begin: index = %u,ts = %ld,offset = %ld,type = %d\n",
//	    header.index_begin.index,header.index_begin.timestamp,header.index_begin.offset,header.index_begin.type);
//    printf("end: index = %u,ts = %ld,offset = %ld,type = %d\n",
//	    header.index_end.index,header.index_end.timestamp,header.index_end.offset,header.index_end.type);
    *pstart = header.index_begin.timestamp;
    *pend = header.index_end.timestamp;
//    printf("tszone start = %ld,end = %ld\n",*pstart,*pend);
    return 0;
}

int mav_read_packet(FILE *fp,struct mav_frame_header *header,unsigned char *frame,int len)
{
    if(fp==NULL || header == NULL || frame==NULL||len<=0)
    {
	printf("invalid parameters!\n");
	return -1;
    }
    int ret = 0;
    long now_offset = ftell(fp);
    struct mav_header_list mavh;
    
    fseek(fp,0,SEEK_SET);
    ret = fread(&mavh,sizeof(struct mav_header_list),1,fp);
    if(ret != 1)
    {
	printf("fread mav header fail,ret = %d\n",ret);
	fseek(fp,now_offset,SEEK_SET);
	return -1;
    }
    else if(mavh.index_offset == sizeof(struct mav_header_list))
    {
	; //SLOG(WRN,"It's a recording file!\n");
    }
    else if(now_offset >= mavh.index_offset)
    {
	printf("fread mav frame end!\n");
	return 0;
    }

    fseek(fp,now_offset,SEEK_SET);
    ret = fread(header,sizeof(struct mav_frame_header),1,fp);
    if(ret!=1)
    {
	printf("read frame header fail,ret = %d\n",ret);
	return -1;
    }
    if(header->len > len)
    {
	printf("frame len[%d] > bufsize[%d]\n",header->len,len);
	//fseek(fp,(-1)*sizeof(struct mav_frame_header),SEEK_CUR);
	fseek(fp,header->len,SEEK_CUR); // skip the frame
	return -1;
    }
    ret = fread(frame,1,header->len,fp);
    if(ret != header->len)
    {
	printf("read frame [%dB] fail!\n",header->len);
	return ret;
    }
    return header->len;
}

int mav_read_frame(const char *filename,struct mav_frame_header *header,unsigned char *frame,int len)
{
    return 0;
}

int mav_get_current_tszone(time_t *pstart,time_t *pend)
{
    if(pstart==NULL || pend == NULL)
	return -1;
    if(pcurrent_mav==NULL)
	return -1;
    
    int index_sum = pcurrent_mav->index->sum;
    *pstart = pcurrent_mav->index->item[0].timestamp;
    *pend = pcurrent_mav->index->item[index_sum-1].timestamp;
    return 0;
}

FILE* mav_seek_current(time_t tnode,time_t *pstart, time_t *ptend)
{
    if(pcurrent_mav==NULL || pstart==NULL || ptend==NULL)
    {
	printf("pcurrent_mav or pstart/ptend is NULL!\n");
	return  NULL;
    }
    /* To avoid @pmav had been freed when record file finished */
    MAV_HANDLE ptmp = malloc(sizeof(*pcurrent_mav)+pcurrent_mav->index_max*sizeof(struct mav_index_list));
    if(ptmp==NULL)
    {
	SLOG(ERR,"malloc pmav backup fail!\n");
	return NULL;
    }
    else
    {
	memcpy(ptmp,pcurrent_mav,sizeof(*ptmp));
	memcpy(ptmp->index,pcurrent_mav->index,sizeof(struct mav_index_list)*ptmp->index_max);
    }

    int index_sum = ptmp->index->sum;
    if(ptmp->fp!=NULL)
    {
	fflush(ptmp->fp);
    }

    if(tnode >= ptmp->index->item[index_sum-1].timestamp)
    {
	SLOG(ERR,"tnode [%ld] has no replay file!\n",tnode);
	return NULL;
    }

    FILE *fp = fopen(ptmp->fname,"rb");
    long offset = 0;

    if(fp == NULL)
    {
	SLOG(ERR,"fopen [%s] err:%s\n",ptmp->fname,strerror(errno));
	return NULL;
    }
    if(tnode <= ptmp->index->item[0].timestamp)
    {
	offset = sizeof(struct mav_header_list);
    }
    else
    {
	int i = 0;
	offset = -1;
	for(i=1;i<index_sum;i++)
	{
	    if(tnode>=ptmp->index->item[i-1].timestamp 
		    && tnode<=ptmp->index->item[i].timestamp)
	    {
		offset = ptmp->index->item[i-1].offset;
		break;
	    }
	}
	if(offset < 0)
	{
	    SLOG(ERR,"mav seek current tnode[%ld] in %s fail!\n",tnode,ptmp->fname);
	    fclose(fp);
	    return NULL;
	}
    }

    *pstart = ptmp->index->item[0].timestamp;
    *ptend = ptmp->index->item[index_sum-1].timestamp;
    fseek(fp,offset,SEEK_SET);
    return fp;
}

int mav_read_prev_packet(FILE *fp,struct mav_frame_header *header,unsigned char *frame,int len)
{
    if(fp==NULL || header == NULL || frame==NULL||len<=0)
    {
	printf("invalid parameters!\n");
	return -1;
    }
    int ret = 0;
    long now_offset = ftell(fp);
    long prev_offset = 0;
    struct mav_header_list mavh;
    
    fseek(fp,0,SEEK_SET);
    ret = fread(&mavh,sizeof(struct mav_header_list),1,fp);
    if(ret != 1)
    {
	printf("fread mav header fail,ret = %d\n",ret);
	fseek(fp,now_offset,SEEK_SET);
	return -1;
    }
    else if(mavh.index_offset == sizeof(struct mav_header_list))
    {
	; //SLOG(WRN,"It's a recording file!\n");
    }
    else if(now_offset < sizeof(struct mav_header_list))
    {
	printf("fread mav frame end!\n");
	return 0;
    }

    /* seek previous frame in the file */
    if(now_offset==sizeof(struct mav_header_list))
	prev_offset = 0;
    else
    {
	unsigned int item_offset = mavh.index_offset+offsetof(struct mav_index_list,item);
	int flag = 0;
	st_mav_index_item node_item[2];
	fseek(fp,item_offset,SEEK_SET);
	while((ret = fread(&node_item[1],sizeof(st_mav_index_item),1,fp))==1)
	{
	    if(flag==0)
	    {
		node_item[0]=node_item[1];
		flag = 1;
	    }
	    if(now_offset==node_item[1].offset)
	    {
		flag = 2;
		break;
	    }
	    node_item[0] = node_item[1]; /* back previous index item */
	}
	if(flag==2)
	    prev_offset = node_item[0].offset;
	else
	    prev_offset = 0;
    }

    fseek(fp,now_offset,SEEK_SET);
    ret = fread(header,sizeof(struct mav_frame_header),1,fp);
    if(ret!=1)
    {
	printf("read frame header fail,ret = %d\n",ret);
	return -1;
    }
    if(header->len > len)
    {
	printf("frame len[%d] > bufsize[%d]\n",header->len,len);
	//fseek(fp,(-1)*sizeof(struct mav_frame_header),SEEK_CUR);
	fseek(fp,header->len,SEEK_CUR); // skip the frame
	return -1;
    }
    ret = fread(frame,1,header->len,fp);
    if(ret != header->len)
    {
	printf("read frame [%dB] fail!\n",header->len);
	return ret;
    }

    fseek(fp,prev_offset,SEEK_SET);
    return header->len;
}
